trimmingBlanks
Strings Sandbox PanosP Revised

Trimming Blanks from a String 

Problem
=======

Remove leading, trailing blanks from a string.

Solution
========

Let's begin with a basic  function to trim "white" characters
from both the begining and the end of a given string:

 function trim(s) {
 	sub(/^[ \t\r\n]+/, "", s)
 	sub(/[ \t\r\n]+$/, "", s)
 	return(s)
 }

Simple enough. Now, let's add a second argument to specify the edge
where the white caracters will be trimmed. That argument will be
optional, defaulting to the  right edge (as this is often the wanted
action).  We will specify the edge(s) using some letters as mnemonics.

  #version1: we'll do it better, below
  function trimStr(s, lrb) {
  	lrb = lrb ? lrb : "r"
  	if (lrb ~ /[lLbB]/) sub(/^[ \t\r\n]+/, "", s)
  	if (lrb ~ /[rRbB]/) sub(/[ \t\r\n]+$/, "", s)
  	return(s)
  } 
 
The second argument (_lrb_) is optional. If we call this function
with only one argument, the second argument _lrb_ will be assigned
the empty string value.  If we have an empty (missing) _lrb_ argument,
we assume the default, that is trim only at the right edge (end) of the
given string (i.e. set it to "r").

(BTW, recall that all the function arguments are copied
in new, local (static) variables inside the function's scope, when
the function is called. So, setting the _lrb_ value to "r" inside the
function doesn't affect any variable outside the function's scope.)

Using these optional arguments as control variables is handy,
but there are some problems. First of
all, let's consider the dangerous situation where we pass an erroneus
_lrb_, e.g. "e" believing it's "r" (fat fingering). With a little
cost we can catch that kind of errors:

  #version2
  function trimStr(s, lrb) {
 	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
 	lrb = lrb ? lrb : "r"
 	if (lrb ~ /[lLbB]/) sub(/^[ \t\r\n]+/, "", s)
 	if (lrb ~ /[rRbB]/) sub(/[ \t\r\n]+$/, "", s)
 	return(s)
  }
 
We begin by checking the _lrb_ argument not to contain anything else
but the valid characters. If anything else matches, then we abort
the whole program, because that's a serious program error and must
be fixed. After that check, we can check for empty _lrb_ value by
checking _(!lrb)_ instead of the less elegant _(lrb != "")_, because
we alredy have checked the case of passing a "0" _lrb_ value by
mistake.

As a last step in constructing the final version of out _trimStr_
function, we provide the means for specifying what we mean as "white"
characters. By default we consider the spaces, tabs, carriage returns
and newlines as "white" characters. But we can also provide some
extra "white" characters if needed, e.g. vertical characters or
form feeds. To achieve such a goal, we can provide a third (optional)
argument to our _trimStr_ function which, if given, will specify the
"white" characters as a string, e.g. if we want to trim just spaces
and tabs, we can provide the _" \t"_ string:

  #version3
  function trimStr(s, lrb, wcs) {
  	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
  	lrb = lrb ? lrb : "r"
  	wcs = wcs ? wcs : " \t\r\n"
  	if (lrb ~ /[lLbB]/) sub("^[" wcs "]+", "", s)
  	if (lrb ~ /[rRbB]/) sub("[" wcs "]+$", "", s)
  	return(s)
  } 

Finally we came up with a small but robust function for trimming
strings. By the way, did you notice the use of dynamic regular
expressions in _sub_ calls? Beyond of robust, our little function
proves to be flexible too. Actually, we can trim characters of any
kind, not just "white" characters, using the _wcs_ argument. If, let's
say, we want to trim any non letter or underscore characters from
the begining of a string, we can just call:

  trimStr(s, "l", "^a-zA-Z_")

In order to make the function even more flexible, we can allow the _wcs_
argument to be a regular expression rather than a plain string.
Doing so, we can provide more complex strings to be trimmed out
from the string edges. The cost is that in the (rare) case where
we call the function to just trim some extra "white" characters,
we have to provide the brackets and the plus symbol too
(remind that the plus symbol is used for repetetion in RE);
but, I believe, that's a
little cost for such a great flexibility! So here, at last, is the
final version:

 # final version
 function trimStr(s, lrb, wcre) {
 	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
 	lrb  = lrb  ? lrb  : "r"
 	wcre = wcre ? wcre : "[ \t\r\n]+"
 	if (lrb ~ /[lLbB]/) sub("^" wcre, "", s)
 	if (lrb ~ /[rRbB]/) sub(wcre "$", "", s)
 	return(s)
 }

The following program will print all strings passed in the input
stream, after cutting out any non letter or underscore characters
from both string edges. The printed strings are printed one string
per line:

 function TrimStr(    x,y,tmp) {	
 	split("  _ 1 2 3 ,arthur", tmp, ",")
 	for(x in tmp) {
 		if (y = trimStr(x, "b", "[^a-zA-Z_]+"))
 			print y
 	}
 }

Author
======

Panos Papadopoulos

